import {
	ref,
	onMounted,
	watch,
	onBeforeUnmount,
	getCurrentInstance,
	nextTick,
	provide,
	inject,
} from 'vue'
import { useRoute } from 'vue-router'
import { Toast } from 'vant'
import { usePageVisibility } from '@vant/use'
import { parseJSON } from '@/utils'
import { PAGE_STATUS } from '@/config'

/**
 * 页面加载动画逻辑
 * @param {*} loadedThreshold 加载完成阈值
 * @param {*} succThreshold 加载成功完成阈值
 * @returns 记录接口加载完成次数函数
 */
export const useLoading = (totalCount = 1, succThreshold) => {
	succThreshold = succThreshold || totalCount
	const _startTime = new Date()
	let _onLoaded
	let _onSucc
	let _onFail
	let _isLoaded = false
	const apiSuccCount = ref(0) // 接口请求成功的数量
	const apiFailCount = ref(0) // 接口请求成功的数量
	const triggerLoad = (isSucc = true) => {
		if (_isLoaded) return
		if (isSucc) {
			apiSuccCount.value++
		} else {
			apiFailCount.value++
		}
		const delayTime = Math.max(100, 500 - new Date() - _startTime)
		// 加载完成
		if (apiSuccCount.value >= succThreshold) {
			_isLoaded = true
			_onSucc && setTimeout(() => _onSucc(), delayTime)
			_onLoaded && setTimeout(() => _onLoaded(), delayTime)
			setTimeout(() => Toast.clear(), delayTime)
			return
		}
		// 判断接口是否全部加载失败，触发onFail事件
		if (apiSuccCount.value + apiFailCount.value >= totalCount) {
			_isLoaded = true
			_onFail && setTimeout(() => _onFail(), delayTime)
			_onLoaded && setTimeout(() => _onLoaded(), delayTime)
			setTimeout(() => Toast.clear(), delayTime)
		}
	}
	const loading = () => {
		// 添加页面加载的loading，默认时长3s
		return Toast.loading({
			message: '加载中...',
			overlay: true,
			duration: 0,
		})
	}
	const openLoading = () => {
		_isLoaded = false
		apiSuccCount.value = 0
		apiFailCount.value = 0
		loading()
	}
	const onLoaded = (fn) => fn && (_onLoaded = fn)
	const onSucc = (fn) => fn && (_onSucc = fn)
	const onFail = (fn) => fn && (_onFail = fn)

	return {
		triggerLoad,
		openLoading,
		onLoaded,
		onSucc,
		onFail,
	}
}

/**
 * 使用页面分享，默认关闭分享
 * @param {*} shareOps 分享参数
 */
export const usePageShare = (shareOps = {}) => {
	const initShare = () => {
		const isUnShare = shareOps?.enable !== 1
		bjsdk.register({
			shareOps: {
				...shareOps,
				enable: isUnShare ? 0 : 1, // 默认禁止分享
				getLink: () => shareOps?.getLink?.() || location.href,
				getTitle: () => shareOps?.getTitle?.() || '',
				getDesc: () => shareOps?.getDesc?.() || '',
				getImg: () =>
					shareOps?.getImg?.() ||
					bjsdk.url('{HomeH5}/share_icon.png', true),
				onShare() {
					bjsdk.isFunc(shareOps?.onShare) && shareOps.onShare()
				},
			},
		})
		// 默认手动禁止右上角分享面板
		if (isUnShare) {
			bjsdk?.setWxMenu?.(0)
		}
	}
	onMounted(initShare)
	// fix从saas页返回本地化页面，右上角分享面板被错误打开，需要再注册一遍分享
	const pageVisibility = usePageVisibility()
	watch(pageVisibility, (isShow) => isShow && initShare())
	const setShare = (invokeKey = '', setShareOps = {}) => {
		bjsdk.setWXShare({
			...shareOps,
			...setShareOps,
			getImg: () =>
				setShareOps?.getImg?.() ||
				shareOps?.getImg?.() ||
				bjsdk.url('{HomeH5}/share_icon.png', true),
			onShare() {
				if (bjsdk.isFunc(setShareOps?.onShare)) {
					setShareOps.onShare()
					return
				}
				if (bjsdk.isFunc(shareOps?.onShare)) {
					shareOps.onShare()
				}
			},
		})
		invokeKey && bjsdk.wxShare(invokeKey)
	}
	return {
		setShare,
	}
}

/**
 * 监控页面滚动元素
 * @param {*} target 被监控监控DOM
 * @returns {Number} scrollTop 实时滚动值
 * @returns {Function} scrollTo 主动跳转到某个滚动位置方法
 */
export const useWatchScroll = (target) => {
	const scrollTop = ref(0)
	let listenerDOM
	let targetDOM
	let listenerFn
	const getScrollParentNode = (parentNode) => {
		if (!parentNode || parentNode === document.documentElement) {
			return document.documentElement
		}
		if (
			['scroll', 'auto'].includes(
				window.getComputedStyle(parentNode).overflowY
			)
		) {
			return parentNode
		}
		return getScrollParentNode(parentNode.parentNode)
	}
	const scrollTo = (y) => {
		if (listenerDOM === window) {
			listenerDOM.scrollTo({
				top: y,
			})
		} else {
			listenerDOM.scrollTop = y
		}
		scrollTop.value = y
	}
	onMounted(() => {
		targetDOM = bjsdk.isStr(target)
			? document.querySelector(target)
			: target
		targetDOM = getScrollParentNode(targetDOM)
		listenerDOM =
			targetDOM === document.documentElement ? window : targetDOM
		listenerDOM.addEventListener(
			'scroll',
			(listenerFn = () => {
				if (listenerDOM === window) {
					scrollTop.value = listenerDOM.scrollY
				} else {
					scrollTop.value = listenerDOM.scrollTop
				}
			})
		)
	})
	onBeforeUnmount(() => listenerDOM.removeEventListener('scroll', listenerFn))
	return {
		scrollTop,
		scrollTo,
	}
}

// 获取组件子级索引路径，通过>连接
const getInnerPath = (instance, children, path = '') => {
	if (!Array.isArray(children)) {
		children = instance.parent?.subTree?.children || []
	}
	if (instance.parent?.subTree?.component === instance) {
		return '0'
	}
	children.some((child, index) => {
		if (child?.component === instance) {
			path = `${path ? `${path}>` : ''}${index}`
			return true
		}
		if (Array.isArray(child?.children) && child.children.length) {
			const _path = getInnerPath(instance, child.children, path)
			if (_path) {
				path = `${path ? `${path}>` : ''}${index}>${_path}`
				return true
			}
		}
	})
	return path
}

// 获取组件父级索引路径，通过|连接
const getCompPath = (instance, path = '') => {
	if (instance === instance.root) {
		return path
	}
	const innerPath = getInnerPath(instance)
	const parentPath = getCompPath(
		instance.parent,
		`${innerPath}${path ? `|${path}` : ''}`
	)
	return parentPath
}

/**
 * 缓存页面数据
 * @param {*} watchedData 被监控页面/组件响应式对象
 * @param {*} scrollContainer 需要被监控滚动DOM元素
 * @param {*} cacheSucc 获取缓存数据成功回调
 * @param {*} cacheFail 获取缓存数据失败回调
 * @param {*} beforeCache 获取缓存数据前的回调函数，返回true走缓存数据缓存并触发cacheSucc，返回false不走缓存数据触发cacheFail
 */
export const usePageCache = ({
	watchedData,
	scrollContainer,
	cacheSucc,
	cacheFail,
	beforeCache,
}) => {
	// 1、获取页面唯一Key值，通过key值获取已缓存的数据
	const instance = getCurrentInstance() // 获取组件实例
	const locationKey = location.pathname + location.search // 获取页面路径
	const pathKey = getCompPath(instance) // 获取组件唯一索引路径
	const compName = instance.type?.__name
	const cacheKey = `__page_cache__${locationKey}__${compName}__${pathKey}` // 拼接页面/组件缓存唯一Key值
	const cachedData = parseJSON(sessionStorage.getItem(cacheKey)) // 获取已缓存数据对象
	const tempData = JSON.parse(JSON.stringify(cachedData)) // 初始化临时数据对象

	// 2、监控页面滚动值
	// 暴露主动滚动页面方法，页面返回时锚点使用
	let scrollTo
	if (scrollContainer) {
		const { scrollTop, scrollTo: _scrollTo } =
			useWatchScroll(scrollContainer)
		scrollTo = _scrollTo
		// 监控滚动值，并复制到临时数据对象
		watch(scrollTop, (y) => {
			tempData.scrollTop = y
		})
	}

	// 3、创建推送缓存数据方法
	// 该方法序列化数据对象进入sessionStorage缓存
	const currentPath = useRoute()?.path // 当前路由路径
	const pushCache = (bjsdkToPath) => {
		// 如果跳转模式为当前页面刷新，则暂时不缓存该页面，只有向别的页面跳转时才缓存
		if (bjsdkToPath === currentPath) {
			return
		}
		sessionStorage.setItem(cacheKey, JSON.stringify(tempData))
	}
	// 订阅全局bjsdk.goTo跳转方法事件，在跳转前调用pushCache方法
	setTimeout(() => {
		// 需要延迟订阅，fix页面进入刚订阅跳转事件就触发
		window._pageCacheGoToHooks = window._pageCacheGoToHooks || {}
		window._pageCacheGoToHooks[locationKey] =
			window._pageCacheGoToHooks[locationKey] || {}
		window._pageCacheGoToHooks[locationKey][cacheKey] = pushCache
	}, 100)
	// 页面/组件销毁时需要销毁已订阅事件
	onBeforeUnmount(() => {
		window._pageCacheGoToHooks?.[locationKey]?.[cacheKey] &&
			delete window._pageCacheGoToHooks[locationKey][cacheKey]
	})

	// 4、提交缓存数据进入临时变量
	// 实时监控传入组件响应式对象，把页面关键渲染数据方法临时数据对象tempData
	const watchedKeys = Object.keys(watchedData)
	const watchedValues = Object.values(watchedData)
	watch(
		watchedValues,
		(list) => {
			list.forEach((item, index) => {
				const key = watchedKeys[index]
				tempData[key] = item
			})
		},
		{
			immediate: true,
		}
	)

	// 5、获取本地缓存数据
	// 如果本地有缓存数据，立即初始化缓存数据
	// 把赋值到传入组件响应式对象，页面通过响应式对象渲染缓存数据对应的DOM
	const compData = Object.keys(cachedData).length ? cachedData : null
	const isParentBeforeSucc = inject('_isParentBeforeSucc')
	const isBeforeSucc =
		[undefined, true].includes(isParentBeforeSucc) &&
		(!beforeCache || beforeCache(compData || {}))
	const canUseCache = compData && isBeforeSucc // 是否可以缓存数据
	provide('_isParentBeforeSucc', isBeforeSucc)
	// 需要延迟清除缓存，预留时间给子组件获取页面缓存数据
	setTimeout(() => sessionStorage.removeItem(cacheKey), 100)

	if (canUseCache) {
		const fromPath = window._routerFrom?.path
		// 如果没有获取到缓存数据，则返回undefined
		// 页面/组件判断没有缓存数据，则会通过接口获取数据渲染页面
		const tabBarRoutes = ['/home', '/material', '/customer', '/mine'] // 4个TAB页路由
		const isTabBarReplace =
			tabBarRoutes.includes(currentPath) &&
			tabBarRoutes.includes(fromPath) // 判断是否为TAB页互相切换
		const watchedEntries = Object.entries(watchedData)
		// 本地有缓存数据，立即赋值到传入组件响应式对象
		// 需要延迟一下赋值
		nextTick(() => {
			watchedEntries.forEach(([stateKey, stateItem]) => {
				if (stateKey in compData) {
					stateItem.value = compData[stateKey]
				}
			})
			// 判断是否有缓存滚动值，则需要滚动到指定位置
			// (需要屏蔽4个TAB切换，TAB页切换需要走缓存，但是页面默认滚动到顶部)
			nextTick(() =>
				setTimeout(() => {
					const y =
						!isTabBarReplace && compData?.scrollTop
							? compData.scrollTop
							: 0
					scrollTo?.(y)
					if ('scrollTop' in tempData) {
						tempData.scrollTop = y
					}
				})
			)
			cacheSucc?.(compData)
		})
	} else {
		cacheFail?.()
	}

	// 6、其他特殊异常处理
	// ios特殊处理逻辑
	// ios回退不刷新页面数据还在，但是需要定位到缓存位置
	// 同时清除本地缓存
	const pageVisibility = usePageVisibility()
	watch(pageVisibility, (val) => {
		if (
			val === 'visible' &&
			pathKey === location.pathname + location.search // pageVisibility会在多个tab页触发，通过判断只在当前路径路由才触发
		) {
			// 需要延迟清除缓存，否则导致ios返回时获取缓存数据时已被清除，导致缓存失效。
			setTimeout(() => sessionStorage.removeItem(cacheKey), 100)
			scrollTo && nextTick(() => scrollTo(tempData.scrollTop))
		}
	})
}
